#pragma once

#include "log.hpp"
#include<iostream>
#include<cstdio>
#include<string>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define SIZE 1024

class UdpServer
{
public:
    // 绑定任意ip后, ip参数可以不写
    UdpServer(uint16_t port, std::string ip = "0.0.0.0"):_port(port),_ip(ip),_sock(-1)
    {}

    // 这里是系统调用，来完成网络功能
    bool initServer()
    {
        // 1. 创建套接字
        // int socket(int domain, int type, int protocol);
        // 参数1: 套接字类型- 网络通信或本地通信
        // 参数2: 套接字的类别 - 面向数据报或面向数据流
        //【与参数1区别:在确定以网络通信或本地通信后,是再以面向数据报还是面向数据流的方式通信呢】
        // 参数3: 前两个参数确定后,就已经确定通信的协议了, 所以此参数忽略
        _sock = socket(AF_INET, SOCK_DGRAM, 0);//网络通信以数据报的形式
        if(_sock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }

        // 2. bind:将用户设置的ip和port在内核中和我们当前的进程强关联
        // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
        // 参数1: 将ip和port与套接字关联
        // 参数2: struct sockaddr *(通用格式) 网络通信强转(struct sockaddr_in*) 本地通信强转(struct sockaddr_un*)
        struct sockaddr_in local; // 里面需要填写三个参数 - 地址类型, 端口号, IP地址
        bzero(&local, 0); // 初始化
        local.sin_family = AF_INET; // (socket中是网络通信)结构体对象将会与网络服务器绑定
        // 服务器的IP和端口未来也是要发送给对方主机的 -> 先要将数据发送到网络！网络是大端字节序
        local.sin_port = htons(_port); // 主机序列转网络序列
        //传入的ip是点分十进制字符串风格的IP地址 "192.168.110.132" 
        // sin_addr是一个结构体里面的s.addr是 uint32_t类型的 且ip为4字节的(所以上面ip需要转为4字节整形的网络序列)
        // local.sin_addr.s_addr =  inet_addr(_ip.c_str()); // inet_addr函数可以直接完成上面两步


        //【服务器建议绑定任意ip】让服务器在工作过程中，可以从任意IP中获取数据
        // 因为: 一台主机会存在多个ip，当绑定具体的ip后，服务器就只能收到发给这个具体ip的消息，如果绑定任意ip，那么
        // 就是告诉操作系统，只要是发给这台主机的指定端口的任意消息都可以接受
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // INADDR_ANY:宏就是0


        
        if(bind(_sock,(struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);  
        }
        logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
        return true;
    }
    void start()
    {
        // 作为一款网络服务器，永远不退出的！
        // 服务器启动-> 进程启动 -> 是一个常驻进程 -> 永远在内存中存在，除非挂了！
         char buffer[SIZE];
         for(;;)
         {
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
            // 参数1:自己的套接字, 参数2:读取的信息放在自定义的缓冲区里面，参数3:缓冲区的长度，参数4:阻塞读取或非阻塞读取
            // 参数5:接受到别人发给我的信息,我有可能还要回复信息，那么就需要知道对方的ip和port,所以src_addr里放的是别人的ip/port
            // 参数5:是输出型参数, 需要自己定义一个这样的结构体, 让对方把信息填进去，这样自己就可以对方的ip和port
            
            struct sockaddr_in peer; // peer,纯输出型参数
            bzero(&peer, sizeof(peer));
            // 【输入时len代表peer缓冲区大小,给别人大小】
            // 【输出时len代表peer被对方填充后的大小】
            socklen_t len = sizeof(peer); // 输入输出性参数
            // start. 读取数据 flag: 阻塞方式
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); 
            
            if(s > 0)
            {
                // 【发生成功进来】
                buffer[s] = 0; // 读入结尾没有\0,需要自己加将数据当作一个字符串
                // 将对方的ip和port填入peer
                uint16_t cli_port = ntohs(peer.sin_port);      // 从网络中来的！-- 需要网络转主机
                std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP -转- 本主机的字符串风格的IP，方便显示
                printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
            }
            
            /*recvfrom收到了数据这里可以做处理数据*/

            // 从peer知道对方的ip和port，可以给方发消息 sendto
            // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
            // 参数1: 自己的套接字，参数2: 存放发的消息，参数3:消息大小，参数3:是否阻塞,参数5: 发给谁,前面recvfrom里的peer就有了
            sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len); // 这里是直接将收到的原封不动的发回去
         }

    }
    ~UdpServer()
    {
        if(_sock >= 0)
            close(_sock);
    }
private:
    // 一个服务器必须要有ip地址和port(16位的整数)
    std::string _ip;
    uint16_t _port;
    int _sock;
};


